#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""This module provides metadata and global data I/O.

It provides ReadMetadataFile API to read metadata file and populate
metadata_in_memory object. It also provides API to read Globaldata file and
upload the data in db.
"""

import logging
import re
import constants
import db_utils
import metadata_in_memory
import sorting_column_sets
import digital_content_wizard_exceptions


def ValidateDisplayFlags(display_flags):
  """Validates display flags and defaults them to NONE if invalid.

  Args:
    display_flags: Array of strings representing display flags.

  Returns:
    Array of validated display flags.
  """
  valid_flags = constants.DISPLAY_FLAGS.keys()
  for i in range(0, len(display_flags)):
    display_flags[i] = display_flags[i].upper()
    if display_flags[i] not in valid_flags:
      display_flags[i] = 'NONE'
  logging.debug('Display Flag: ')
  logging.debug(display_flags)
  return display_flags


def IsContentTypeValid(content_type):
  """Validates the content type and returns the result.

  Args:
    content_type: String representing the content type specified.

  Returns:
    boolean indicating whether the content type is valid.
  """
  valid = content_type.upper() in constants.CONTENT_TYPES
  return valid


def ReadLine(line):
  """Cleans up trailing whitespaces of the line and converts str to unicode.

  This function converts string to unicode string, cleans up the string and
  returns the array of individual column contents.

  Args:
    line: Raw line string.

  Returns:
    Array of column contents or array containing just empty string in case of
    any error during unicode conversion.
  """
  try:
    line_unicode = unicode(line, constants.ENCODING, errors='strict')
  except ValueError:
    return ['']
  line_without_endchar = line_unicode
  if line_unicode[len(line_unicode)-1:] == constants.LINE_SEPARATOR:
    line_without_endchar = line_unicode[0:len(line_unicode)-1]
  column_contents = line_without_endchar.split(constants.FIELD_SEPARATOR)
  return column_contents


def ReadMetadataFile(file_handle, version_id):
  """Reads metadata file and store all the rows into metadata_in_memory object.

  Args:
    file_handle: File handle for tab seaprated metadata file.
    version_id: Version number of the newly created version for which the
    metadata is uploaded.

  Returns:
    Count of number of rows in the file.

  Raises:
    InvalidContentTypeException: An error occurred when invalid content type is
    provided.
    InvalidMetadataException: An error occurred when invalid metadata file is
    provided.
  """
  db_handler = db_utils.DbHandler()
  separator = constants.FIELD_SEPARATOR
  page_parent = db_handler.GetParent(version_id)
  metadata_in_memory_instance = metadata_in_memory.MetadataInMemory()
  metadata_in_memory_instance.Clear()
  set_manager = sorting_column_sets.SetManager()
  # Titles row. Read the row and create metadata from row 0.
  titles = ReadLine(file_handle.readline())
  logging.debug('Title:')
  logging.debug(titles)
  if len(titles) < 6:
    logging.error('Invalid Metadata File')
    raise digital_content_wizard_exceptions.InvalidMetadataException(
        'Invalid metadata header', 1)
  metadata_object = db_handler.CreateMetadata(0, 'id', version_id, titles[1],
                                              titles[2], titles[3], titles[4],
                                              separator.join(titles[5:]),
                                              page_parent)
  metadata_in_memory_instance.AddRow(metadata_object)
  # Descriptions row; skip.
  file_handle.readline()
  # Level3 page display option.
  display_flags = ReadLine(file_handle.readline())
  display_flags = ValidateDisplayFlags(display_flags)
  metadata_object = db_handler.CreateMetadata(-1, 'display_flags', version_id,
                                              display_flags[1],
                                              display_flags[2],
                                              display_flags[3],
                                              display_flags[4],
                                              separator.join(display_flags[5:]),
                                              page_parent)
  metadata_in_memory_instance.AddRow(metadata_object)

  content_ids = []
  count = 0
  for line in file_handle:
    column_contents = ReadLine(line)
    if len(column_contents) == 1:
      # End of file.
      break
    if len(column_contents) > 5:
      # Consider line only if all the required columns are present.
      (metadata_id, first_sorting_column, second_sorting_column,
       third_sorting_column, title) = column_contents[0:5]
      metadata = separator.join(column_contents[5:])
      if not IsContentTypeValid(column_contents[5]):
        raise digital_content_wizard_exceptions.InvalidContentTypeException(
            'Invalid Content Type', count + 4)
      if metadata_id == '':
        raise digital_content_wizard_exceptions.InvalidMetadataException(
            'Content id cannot be empty', count + 4)
      if first_sorting_column == '':
        raise digital_content_wizard_exceptions.InvalidMetadataException(
            'First sorting column value cannot be empty', count + 4)
      if second_sorting_column == '':
        raise digital_content_wizard_exceptions.InvalidMetadataException(
            'Second sorting column value cannot be empty', count + 4)
      if title == '':
        raise digital_content_wizard_exceptions.InvalidMetadataException(
            'Title column value cannot be empty', count + 4)
      if metadata_id in content_ids:
        raise digital_content_wizard_exceptions.InvalidMetadataException(
            'Content id should be unique', count + 4)
      content_ids.append(metadata_id)
      metadata_object = db_handler.CreateMetadata(
          count+1,
          metadata_id,
          version_id,
          first_sorting_column,
          second_sorting_column,
          third_sorting_column,
          title,
          metadata,
          page_parent)
      metadata_in_memory_instance.AddRow(metadata_object)
      set_manager.UpdateColumnSets(metadata_id, first_sorting_column,
                                   second_sorting_column, third_sorting_column)
    else:
      raise digital_content_wizard_exceptions.InvalidMetadataException(
          'Invalid metadata format', count + 4)
    count += 1

  set_manager.PrintUniqueValues()
  return count


def GetGlobalData(file_handle, version_id):
  """Reads the global data file and populates the data into AppEngine db.

  Args:
     file_handle: File handle to the global data file.
     version_id: Version number, to which this global data needs to be
     populated.

  Raises:
    InvalidGlobaldataException: An error occurred when invalid global data file
    is provided.
  """
  globaldata_rows = []

  db_handler = db_utils.DbHandler()
  page_parent = db_handler.GetParent(version_id)

  global_data = file_handle.read()
  # Get the columns from tab separated file
  cells = global_data.split(constants.FIELD_SEPARATOR)
  if len(cells) < 2:
    raise digital_content_wizard_exceptions.InvalidGlobaldataException(
        'Invalid globaldata format', 1)
  # As the global data file contains 3 fields in a line, 3rd field would
  # actually contain first field of the next row. So separate out that first
  # field of the next row.
  fields = cells[2].split(constants.LINE_SEPARATOR)
  if len(fields) != 2:
    raise digital_content_wizard_exceptions.InvalidGlobaldataException(
        'Invalid globaldata format', 1)
  next = fields[1]
  if cells[0] != 'Type':
    raise digital_content_wizard_exceptions.InvalidGlobaldataException(
        'Invalid globaldata format', 1)
  del cells[0:3]
  length = len(cells)
  row_id = 1
  while 1:
    if length == 0:
      break
    if length < 2:
      raise digital_content_wizard_exceptions.InvalidGlobaldataException(
          'Invalid globaldata format', row_id + 1)
    type_gdata = next
    value = cells[0]
    fields = cells[1].split(constants.LINE_SEPARATOR)
    variable = fields[0]
    if len(fields) == 2:
      next = fields[1]
    match = re.search(r'^\w+$', variable)
    if not match:
      # Variable name is invalid. This might be due to missing one of the
      # columns in global data tsv.
      raise digital_content_wizard_exceptions.InvalidGlobaldataException(
          'Invalid globaldata format', row_id + 1)
    globaldata_obj = db_handler.CreateGlobalData(
        row_id,
        type_gdata,
        value,
        variable,
        version_id,
        page_parent)
    globaldata_rows.append(globaldata_obj)
    row_id += 1
    length -= 2
    del cells[0:2]

  db_handler.AddBulkGlobalData(globaldata_rows)
